home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / sources / unix / volume25 / trn / part11 < prev    next >
Encoding:
Internet Message Format  |  1991-12-02  |  38.6 KB

  1. Subject:  v25i014:  trn 2.0 - threaded newsreader based on rn 4.4, Part11/13
  2. Newsgroups: comp.sources.unix
  3. Approved: vixie@pa.dec.com
  4.  
  5. Submitted-by: davison@borland.com (Wayne Davison)
  6. Posting-number: Volume 25, Issue 14
  7. Archive-name: trn/part11
  8.  
  9. #! /bin/sh
  10. # This is a shell archive.  Remove anything before this line, then unpack
  11. # it by saving it into a file and typing "sh file".  To overwrite existing
  12. # files, type "sh file -c".  You can also feed this as standard input via
  13. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  14. # will see the following message at the end:
  15. #        "End of archive 11 (of 13)."
  16. # Contents:  mt-process.c
  17. # Wrapped by vixie@cognition.pa.dec.com on Tue Dec  3 16:34:57 1991
  18. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  19. if test -f 'mt-process.c' -a "${1}" != "-c" ; then 
  20.   echo shar: Will not clobber existing file \"'mt-process.c'\"
  21. else
  22. echo shar: Extracting \"'mt-process.c'\" \(36510 characters\)
  23. sed "s/^X//" >'mt-process.c' <<'END_OF_FILE'
  24. X/* $Id: mt-process.c,v 4.4.3.1 1991/11/22 04:12:15 davison Trn $
  25. X**
  26. X** $Log: mt-process.c,v $
  27. X** Revision 4.4.3.1  1991/11/22  04:12:15  davison
  28. X** Trn Release 2.0
  29. X** 
  30. X*/
  31. X
  32. X#include "EXTERN.h"
  33. X#include "common.h"
  34. X#include "threads.h"
  35. X#include "mthreads.h"
  36. X#include "ndir.h"
  37. X#ifdef SERVER
  38. X#include "server.h"
  39. X#endif
  40. X#include "INTERN.h"
  41. X#include "bits.h"
  42. X
  43. X#include <time.h>
  44. X#ifndef TZSET
  45. X# include <sys/timeb.h>
  46. X#endif
  47. X
  48. X#if defined(SERVER) && !defined(USLEEP)
  49. X# include <sys/time.h>
  50. X#endif
  51. X
  52. X#define buff buf
  53. X
  54. char references[1024];
  55. X
  56. char subject_str[80];
  57. bool found_Re;
  58. X
  59. char author_str[20];
  60. X
  61. extern int log_verbosity, slow_down;
  62. X
  63. long num;
  64. X
  65. DOMAIN *next_domain;
  66. X
  67. void insert_article(), expire(), trim_roots(), order_roots(), trim_authors();
  68. void make_root(), use_root(), merge_roots(), set_root(), unlink_root();
  69. void link_child(), unlink_child();
  70. void free_article(), free_domain(), free_subject(), free_root(), free_author();
  71. void get_subject_str(), get_author_str();
  72. ARTICLE *get_article();
  73. SUBJECT *new_subject();
  74. AUTHOR *new_author();
  75. X
  76. X#ifdef TZSET
  77. extern time_t tnow;
  78. extern long timezone;
  79. X#else
  80. extern struct timeb ftnow;
  81. X#endif
  82. X
  83. X#ifndef SERVER
  84. static FILE *fp_article;
  85. X#endif
  86. X
  87. X/* Given the upper/lower bounds of the articles in the current group, add all
  88. X** the ones that we don't know about and remove all the ones that have expired.
  89. X** The current directory must be the newsgroup's spool directory.
  90. X*/
  91. void
  92. process_articles(first_article, last_article)
  93. ART_NUM first_article, last_article;
  94. X{
  95. X    register char *cp, *str;
  96. X    register ARTICLE *article;
  97. X    register ART_NUM i;
  98. X    time_t date;
  99. X    bool has_xrefs;
  100. X    int len;
  101. X#ifdef SERVER
  102. X    bool orig_extra = extra_expire;
  103. X#endif
  104. X#ifdef TMPTHREAD
  105. X    extern int start;
  106. X#else
  107. X    int start = total.last + 1;
  108. X#endif
  109. X    extern int errno;
  110. X    extern int sys_nerr;
  111. X    extern char *sys_errlist[];
  112. X
  113. X    if (first_article > start) {
  114. X    start = first_article;
  115. X    }
  116. X    added_count = last_article - start + 1;
  117. X    if (added_count < 0) {
  118. X    added_count = 0;
  119. X    } else if (added_count > 1000) {
  120. X    /* Don't overwork ourselves the first time */
  121. X    added_count = 1000;
  122. X    start = last_article - 1000 + 1;
  123. X    }
  124. X    expired_count = 0;
  125. X
  126. X#ifdef TMPTHREAD
  127. X    if (added_count) {
  128. X    printf("\nThreading %d article%s...", added_count,
  129. X        added_count == 1 ? nullstr : "s"), fflush(stdout);
  130. X    }
  131. X#endif
  132. X
  133. X    for (i = start; i <= last_article; i++) {
  134. X#ifdef TMPTHREAD
  135. X    if ((i - start) % 20 == 0) {
  136. X        if (i - start) {
  137. X        printf("%d...", i - start), fflush(stdout);
  138. X        }
  139. X    }
  140. X#endif
  141. X#ifdef SERVER
  142. X    if (slow_down) {
  143. X        usleep(slow_down);
  144. X    }
  145. X    sprintf(buff, "HEAD %ld", (long)i);
  146. X    put_server(buff);
  147. X    if (get_server(buff, sizeof buff) < 0 || *buff == CHAR_FATAL) {
  148. X        last_article = i - 1;
  149. X        extra_expire = FALSE;
  150. X        break;
  151. X    }
  152. X    if (*buff != CHAR_OK) {
  153. X        added_count--;
  154. X        continue;
  155. X    }
  156. X#else
  157. X    /* Open article in current directory. */
  158. X    sprintf(buff, "%ld", (long)i);
  159. X    /* Set errno for purely paranoid reasons */
  160. X    errno = 0;
  161. X    if ((fp_article = fopen(buff, "r")) == Nullfp) {
  162. X        /* Missing files are ok -- they've just been expired or canceled */
  163. X        if (errno != 0 && errno != ENOENT) {
  164. X        if (errno < 0 || errno > sys_nerr) {
  165. X            log_error("Can't open `%s': Error %d.\n", buff, errno);
  166. X        } else {
  167. X            log_error("Can't open `%s': %s.\n", buff,
  168. X              sys_errlist[errno]);
  169. X        }
  170. X        }
  171. X        added_count--;
  172. X        continue;
  173. X    }
  174. X#endif
  175. X
  176. X    article = Nullart;
  177. X    *references = '\0';
  178. X    *author_str = '\0';
  179. X    *subject_str = '\0';
  180. X    found_Re = 0;
  181. X    date = 0;
  182. X    has_xrefs = FALSE;
  183. X
  184. X#ifdef SERVER
  185. X    while (get_server(cp = buff, sizeof buff) == 0) {
  186. X      process_line:
  187. X        if (*cp == '.') {
  188. X        if (cp[1]) {
  189. X            log_error("Header line starts with '.'! [%ld].\n",
  190. X                (long)i);
  191. X            continue;
  192. X        }
  193. X        break;
  194. X        }
  195. X#else
  196. X    while ((cp = fgets(buff, sizeof buff, fp_article)) != Nullch) {
  197. X      process_line:
  198. X        if (*cp == '\n') {        /* check for end of header */
  199. X        break;            /* break out when found */
  200. X        }
  201. X#endif
  202. X        if ((unsigned char)*cp <= ' ') {     /* skip continuation lines */
  203. X        continue;        /* (except references -- see below) */
  204. X        }
  205. X        if ((str = index(cp, ':')) == Nullch) {
  206. X#ifdef SERVER
  207. X        if (log_verbosity) {
  208. X            log_error("Header line missing colon! [%ld].\n", (long)i);
  209. X        }
  210. X        continue;        /* skip bogus header line */
  211. X#else
  212. X        break;            /* end of header if no colon found */
  213. X#endif
  214. X        }
  215. X        if ((len = str - cp) > 10) {
  216. X        continue;        /* skip keywords > 10 chars */
  217. X        }
  218. X#ifndef SERVER
  219. X        cp[strlen(cp)-1] = '\0';    /* remove newline */
  220. X#endif
  221. X        while (cp < str) {        /* lower-case the keyword */
  222. X        if ((unsigned char)*cp <= ' ') { /* stop at any whitespace */
  223. X            break;
  224. X        }
  225. X        if (isupper(*cp)) {
  226. X            *cp = tolower(*cp);
  227. X        }
  228. X        cp++;
  229. X        }
  230. X        *cp = '\0';
  231. X        cp = buff;
  232. X        if (len == 4 && strEQ(cp, "date")) {
  233. X#ifdef TZSET
  234. X            date = get_date(str + 1, tnow, timezone);
  235. X#else
  236. X        date = get_date(str + 1, ftnow.time, (long) ftnow.timezone);
  237. X#endif
  238. X        } else
  239. X        if (len == 4 && strEQ(cp, "from")) {
  240. X        get_author_str(str + 1);
  241. X        } else
  242. X        if (len == 4 && strEQ(cp, "xref")) {
  243. X        has_xrefs = TRUE;
  244. X        } else
  245. X        if (len == 7 && strEQ(cp, "subject")) {
  246. X        get_subject_str(str + 1);
  247. X        } else
  248. X        if (len == 10 && strEQ(cp, "message-id")) {
  249. X        if (!article) {
  250. X            article = get_article(str + 1);
  251. X        } else {
  252. X            if (log_verbosity) {
  253. X            log_error("Found multiple Message-IDs! [%ld].\n",
  254. X                (long)i);
  255. X            }
  256. X        }
  257. X        } else
  258. X        if (len == 10 && strEQ(cp, "references")) {
  259. X        /* include preceding space in saved reference */
  260. X        len = strlen(str + 1);
  261. X        bcopy(str + 1, references, len + 1);
  262. X        str = references + len;
  263. X        /* check for continuation lines */
  264. X#ifdef SERVER
  265. X        while (get_server(cp = buff, sizeof buff) == 0) {
  266. X#else
  267. X        while ((cp = fgets(buff, sizeof buff, fp_article)) != Nullch) {
  268. X#endif
  269. X            if (*cp != ' ' && *cp != '\t') {
  270. X            goto process_line;
  271. X            }
  272. X            while (*++cp == ' ' || *cp == '\t') {
  273. X            ;
  274. X            }
  275. X            *--cp = ' ';
  276. X            /* If the references are too long, shift them over to
  277. X            ** always save the most recent ones.
  278. X            */
  279. X            if ((len += strlen(cp)) > 1023) {
  280. X            strcpy(buff, buff + len - 1023);
  281. X            str -= len - 1023;
  282. X            len = 1023;
  283. X            }
  284. X            strcpy(str, cp);
  285. X        }/* while */
  286. X        break;
  287. X        }/* if */
  288. X    }/* while */
  289. X    if (article) {
  290. X        num = i;
  291. X        insert_article(article, date);
  292. X        if (has_xrefs) {
  293. X        article->flags |= HAS_XREFS;
  294. X        }
  295. X    } else {
  296. X        if (log_verbosity) {
  297. X        log_error("Message-ID line missing! [%ld].\n", (long)i);
  298. X        }
  299. X    }
  300. X#ifndef SERVER
  301. X    fclose(fp_article);
  302. X#endif
  303. X    }
  304. X
  305. X    if (extra_expire || first_article > total.first) {
  306. X    absfirst = first_article;
  307. X    lastart = last_article;
  308. X    expire(first_article <= last_article ? extra_expire : FALSE);
  309. X    }
  310. X    trim_roots();
  311. X    order_roots();
  312. X    trim_authors();
  313. X
  314. X    total.first = first_article;
  315. X    total.last = last_article;
  316. X#ifdef SERVER
  317. X    extra_expire = orig_extra;
  318. X#endif
  319. X}
  320. X
  321. X/* Search all articles for numbers less than new_first.  Traverse the list
  322. X** using the domain links so we don't have to deal with the tree structure.
  323. X** If extra is true, list all articles in the directory to setup a bitmap
  324. X** with the existing articles marked as 'read', and drop everything that
  325. X** isn't there.
  326. X*/
  327. void
  328. expire(extra)
  329. bool_int extra;
  330. X{
  331. X    register DOMAIN *domain;
  332. X    register ARTICLE *article, *next_art, *hold;
  333. X#ifndef TMPTHREAD
  334. X    register ART_NUM art;
  335. X#ifndef SERVER
  336. X    register DIR *dirp;
  337. X#endif
  338. X#endif
  339. X
  340. X#ifdef TMPTHREAD
  341. X    extra = FALSE;
  342. X#else
  343. X    if (extra) {
  344. X      MEM_SIZE ctlsize;
  345. X
  346. X    /* Allocate a bitmap large enough for absfirst thru lastart. */
  347. X#ifndef lint
  348. X    ctlsize = (MEM_SIZE)(OFFSET(lastart)/BITSPERBYTE+20);
  349. X#endif
  350. X    ctlarea = safemalloc(ctlsize);
  351. X    bzero(ctlarea, ctlsize);
  352. X
  353. X    /* List all articles and use ctl_set() to keep track of what's there. */
  354. X#ifdef SERVER
  355. X    sprintf(buff, "XHDR message-id %ld-%ld", (long)absfirst, (long)lastart);
  356. X    put_server(buff);
  357. X    if (get_server(buff, sizeof buff) == 0 && *buff == CHAR_OK) {
  358. X        while (1) {
  359. X        if (get_server(buff, sizeof buff) < 0) {
  360. X            extra = 0;
  361. X            break;
  362. X        }
  363. X        if (*buff == '.') {
  364. X            break;
  365. X        }
  366. X        art = atol(buff);
  367. X        if (art >= absfirst && art <= lastart) {
  368. X            ctl_set(art);
  369. X        }
  370. X        }
  371. X    } else {
  372. X        extra = 0;
  373. X    }
  374. X#else
  375. X    if ((dirp = opendir(".")) != 0) {
  376. X      register struct DIRTYPE *dp;
  377. X
  378. X        while ((dp = readdir(dirp)) != Null(struct DIRTYPE *)) {
  379. X          register char *p;
  380. X
  381. X        for (p = dp->d_name; *p; p++) {
  382. X            if (!isdigit(*p)) {
  383. X            goto nope;
  384. X            }
  385. X        }
  386. X        art = atol(dp->d_name);
  387. X        if (art >= absfirst && art <= lastart) {
  388. X            ctl_set(art);
  389. X        }
  390. X      nope: ;
  391. X        }
  392. X        closedir(dirp);
  393. X    } else {
  394. X        extra = 0;
  395. X    }
  396. X#endif
  397. X    } else {
  398. X    ctlarea = Nullch;
  399. X    }
  400. X#endif /* TMPTHREAD */
  401. X
  402. X    for (domain = &unk_domain; domain; domain = next_domain) {
  403. X    next_domain = domain->link;
  404. X    for (article = domain->ids; article; article = next_art) {
  405. X        next_art = article->id_link;
  406. X        if (!article->subject) {
  407. X        continue;
  408. X        }
  409. X        if (article->num < absfirst
  410. X#ifndef TMPTHREAD
  411. X         || (extra && !ctl_read(article->num))
  412. X#endif
  413. X       ) {
  414. X        article->subject->count--;
  415. X        article->subject = 0;
  416. X        article->flags &= ~HAS_XREFS;
  417. X        article->author->count--;
  418. X        article->author = 0;
  419. X        /* Free expired article if it has no children.  Then check
  420. X        ** if the parent(s) are also fake and can be freed.  We'll
  421. X        ** free any empty roots later.
  422. X        */
  423. X        while (!article->children) {
  424. X            hold = article->parent;
  425. X            unlink_child(article);
  426. X            free_article(article);
  427. X            if (hold && !hold->subject) {
  428. X            if ((article = hold) == next_art) {
  429. X                next_art = next_art->id_link;
  430. X            }
  431. X            } else {
  432. X            break;
  433. X            }
  434. X        }
  435. X        expired_count++;
  436. X        }/* if */
  437. X    }/* for */
  438. X    }/* for */
  439. X    next_domain = Null(DOMAIN*);
  440. X
  441. X#ifndef TMPTHREAD
  442. X    safefree(&ctlarea);
  443. X#endif
  444. X}
  445. X
  446. X/* Trim the article chains down so that we don't have more than one faked
  447. X** article between the root and any real ones.
  448. X*/
  449. void
  450. trim_roots()
  451. X{
  452. X    register ROOT *root, *last_root;
  453. X    register ARTICLE *article, *next;
  454. X    register SUBJECT *subject, *last_subj;
  455. X    register int found;
  456. X
  457. X#ifndef lint
  458. X    last_root = (ROOT *)&root_root;
  459. X#else
  460. X    last_root = Null(ROOT*);
  461. X#endif
  462. X    for (root = root_root; root; root = last_root->link) {
  463. X    for (article = root->articles; article; article = article->siblings) {
  464. X        /* If an article has no subject, it is a "fake" reference node.
  465. X        ** If all of its immediate children are also fakes, delete it
  466. X        ** and graduate the children to the root.  If everyone is fake,
  467. X        ** the chain dies.
  468. X        */
  469. X        while (!article->subject) {
  470. X        found = 0;
  471. X        for (next = article->children; next; next = next->siblings) {
  472. X            if (next->subject) {
  473. X            found = 1;
  474. X            break;
  475. X            }
  476. X        }
  477. X        if (!found) {
  478. X            /* Remove this faked article and move all its children
  479. X            ** up to the root.
  480. X            */
  481. X            next = article->children;
  482. X            unlink_child(article);
  483. X            free_article(article);
  484. X            for (article = next; article; article = next) {
  485. X            next = article->siblings;
  486. X            article->parent = Nullart;
  487. X            link_child(article);
  488. X            }
  489. X            article = root->articles;    /* start this root over */
  490. X        } else {
  491. X            break;            /* else, on to next article */
  492. X        }
  493. X        }
  494. X    }
  495. X    /* Free all unused subject strings.  Begin by trying to find a
  496. X    ** subject for the root's pointer.
  497. X    */
  498. X    for (subject = root->subjects; subject && !subject->count; subject = root->subjects) {
  499. X        root->subjects = subject->link;
  500. X        free_subject(subject);
  501. X        root->subject_cnt--;
  502. X    }
  503. X    /* Then free up any unused intermediate subjects.
  504. X    */
  505. X    if ((last_subj = subject) != Null(SUBJECT*)) {
  506. X        while ((subject = subject->link) != Null(SUBJECT*)) {
  507. X        if (!subject->count) {
  508. X            last_subj->link = subject->link;
  509. X            free_subject(subject);
  510. X            root->subject_cnt--;
  511. X            subject = last_subj;
  512. X        } else {
  513. X            last_subj = subject;
  514. X        }
  515. X        }
  516. X    }
  517. X    /* Now, free all roots without articles.  Flag unexpeced errors.
  518. X    */
  519. X    if (!root->articles) {
  520. X        if (root->subjects) {
  521. X        log_error("** Empty root still had subjects remaining! **\n");
  522. X        }
  523. X        last_root->link = root->link;
  524. X        free_root(root);
  525. X    } else {
  526. X        last_root = root;
  527. X    }
  528. X    }
  529. X}
  530. X
  531. X/* Descend the author list, find any author names that aren't used
  532. X** anymore and free them.
  533. X*/
  534. void
  535. trim_authors()
  536. X{
  537. X    register AUTHOR *author, *last_author;
  538. X
  539. X#ifndef lint
  540. X    last_author = (AUTHOR *)&author_root;
  541. X#else
  542. X    last_author = Null(AUTHOR*);
  543. X#endif
  544. X    for (author = author_root; author; author = last_author->link) {
  545. X    if (!author->count) {
  546. X        last_author->link = author->link;
  547. X        free_author(author);
  548. X    } else {
  549. X        last_author = author;
  550. X    }
  551. X    }
  552. X}
  553. X
  554. X/* Reorder the roots to place the oldest ones first (age determined by
  555. X** date of oldest article).
  556. X*/
  557. void
  558. order_roots()
  559. X{
  560. X    register ROOT *root, *next, *search;
  561. X
  562. X    /* If we don't have at least two roots, we're done! */
  563. X    if (!(root = root_root) || !(next = root->link)) {
  564. X    return;                        /* RETURN */
  565. X    }
  566. X    /* Break the old list off after the first root, and then start
  567. X    ** inserting the roots into the list by date.
  568. X    */
  569. X    root->link = Null(ROOT*);
  570. X    while ((root = next) != Null(ROOT*)) {
  571. X    next = next->link;
  572. X    if ((search = root_root)->articles->date >= root->articles->date) {
  573. X        root->link = root_root;
  574. X        root_root = root;
  575. X    } else {
  576. X        while (search->link
  577. X         && search->link->articles->date < root->articles->date) {
  578. X        search = search->link;
  579. X        }
  580. X        root->link = search->link;
  581. X        search->link = root;
  582. X    }
  583. X    }
  584. X}
  585. X
  586. X#define EQ(x,y) ((isupper(x) ? tolower(x) : (x)) == (y))
  587. X
  588. X/* Parse the subject into 72 characters or less.  Remove any "Re[:^]"s from
  589. X** the front (noting that it's there), and any "(was: old)" stuff from
  590. X** the end.  Then, compact multiple whitespace characters into one space,
  591. X** trimming leading/trailing whitespace.  If it's still too long, unmercifully
  592. X** cut it off.  We don't bother with subject continuation lines either.
  593. X*/
  594. void
  595. get_subject_str(str)
  596. register char *str;
  597. X{
  598. X    register char *cp;
  599. X    register int len;
  600. X
  601. X    while (*str && (unsigned char)*str <= ' ') {
  602. X    str++;
  603. X    }
  604. X    if (!*str) {
  605. X    bcopy("<None>", subject_str, 7);
  606. X    return;                        /* RETURN */
  607. X    }
  608. X    cp = str;
  609. X    while (EQ(cp[0], 'r') && EQ(cp[1], 'e')) {    /* check for Re: */
  610. X    cp += 2;
  611. X    if (*cp == '^') {                /* allow Re^2: */
  612. X        while (*++cp <= '9' && *cp >= '0') {
  613. X        ;
  614. X        }
  615. X    }
  616. X    if (*cp != ':') {
  617. X        break;
  618. X    }
  619. X    while (*++cp == ' ') {
  620. X        ;
  621. X    }
  622. X    found_Re = 1;
  623. X    str = cp;
  624. X    }
  625. X    /* Remove "(was: oldsubject)", because we already know the old subjects.
  626. X    ** Also match "(Re: oldsubject)".  Allow possible spaces after the ('s.
  627. X    */
  628. X    for (cp = str; (cp = index(cp+1, '(')) != Nullch;) {
  629. X    while (*++cp == ' ') {
  630. X        ;
  631. X    }
  632. X    if (EQ(cp[0], 'w') && EQ(cp[1], 'a') && EQ(cp[2], 's')
  633. X     && (cp[3] == ':' || cp[3] == ' '))
  634. X    {
  635. X        *--cp = '\0';
  636. X        break;
  637. X    }
  638. X    if (EQ(cp[0], 'r') && EQ(cp[1], 'e')
  639. X     && ((cp[2]==':' && cp[3]==' ') || (cp[2]=='^' && cp[4]==':'))) {
  640. X        *--cp = '\0';
  641. X        break;
  642. X    }
  643. X    }
  644. X    /* Copy subject to a temporary string, compacting multiple spaces/tabs */
  645. X    for (len = 0, cp = subject_str; len < 72 && *str; len++) {
  646. X    if ((unsigned char)*str <= ' ') {
  647. X        while (*++str && (unsigned char)*str <= ' ') {
  648. X        ;
  649. X        }
  650. X        *cp++ = ' ';
  651. X    } else {
  652. X        *cp++ = *str++;
  653. X    }
  654. X    }
  655. X    if (cp[-1] == ' ') {
  656. X    cp--;
  657. X    }
  658. X    *cp = '\0';
  659. X}
  660. X
  661. X/* Try to fit the author name in 16 bytes.  Use the comment portion in
  662. X** parenthesis if present.  Cut off non-commented names at the '@' or '%'.
  663. X** Then, put as many characters as we can into the 16 bytes, packing multiple
  664. X** whitespace characters into a single space.
  665. X** We might want to implement a nice name shortening algorithm sometime.
  666. X*/
  667. void
  668. get_author_str(str)
  669. char *str;
  670. X{
  671. X    register char *cp, *cp2;
  672. X
  673. X    if ((cp = index(str, '(')) != Nullch) {
  674. X    str = cp+1;
  675. X    if ((cp = rindex(str, ')')) != Nullch) {
  676. X        *cp = '\0';
  677. X    }
  678. X    } else {
  679. X    if ((cp = index(str, '@')) != Nullch) {
  680. X        *cp = '\0';
  681. X    }
  682. X    if ((cp = index(str, '%')) != Nullch) {
  683. X        *cp = '\0';
  684. X    }
  685. X    }
  686. X    for (cp = str, cp2 = author_str; *cp && cp2-author_str < 16;) {
  687. X    /* Pack white space and turn ctrl-chars into spaces. */
  688. X    if (*cp <= ' ') {
  689. X        while (*++cp && *cp <= ' ') {
  690. X        ;
  691. X        }
  692. X        if (cp2 != author_str) {
  693. X        *cp2++ = ' ';
  694. X        }
  695. X    } else {
  696. X        *cp2++ = *cp++;
  697. X    }
  698. X    }
  699. X    *cp2 = '\0';
  700. X}
  701. X
  702. X/* Take a message-id and see if we already know about it.  If so, return it.
  703. X** If not, create it.  We separate the id into its id@domain parts, and
  704. X** link all the unique ids to one copy of the domain portion.  This saves
  705. X** a bit of space.
  706. X*/
  707. ARTICLE *
  708. get_article(msg_id)
  709. char *msg_id;
  710. X{
  711. X    register DOMAIN *domain;
  712. X    register ARTICLE *article;
  713. X    register char *cp, *after_at;
  714. X
  715. X    /* Take message id, break it up into <id@domain>, and try to match it.
  716. X    */
  717. X    while (*msg_id == ' ') {
  718. X    msg_id++;
  719. X    }
  720. X    cp = msg_id + strlen(msg_id) - 1;
  721. X    if (msg_id >= cp) {
  722. X    if (log_verbosity) {
  723. X        log_error("Message-ID is empty! [%ld]\n", num);
  724. X    }
  725. X    return Nullart;
  726. X    }
  727. X    if (*msg_id++ != '<') {
  728. X    if (log_verbosity) {
  729. X        log_error("Message-ID doesn't start with '<' [%ld]\n", num);
  730. X    }
  731. X    msg_id--;
  732. X    }
  733. X    if (*cp != '>') {
  734. X    if (log_verbosity) {
  735. X        log_error("Message-ID doesn't end with '>' [%ld]\n", num);
  736. X    }
  737. X    cp++;
  738. X    }
  739. X    *cp = '\0';
  740. X    if (msg_id == cp) {
  741. X    if (log_verbosity) {
  742. X        log_error("Message-ID is null! [%ld]\n", num);
  743. X    }
  744. X    return Nullart;
  745. X    }
  746. X
  747. X    if ((after_at = index(msg_id, '@')) == Nullch) {
  748. X    domain = &unk_domain;
  749. X    } else {
  750. X    *after_at++ = '\0';
  751. X    for (cp = after_at; *cp; cp++) {
  752. X        if (isupper(*cp)) {
  753. X        *cp = tolower(*cp);        /* lower-case domain portion */
  754. X        }
  755. X    }
  756. X    *cp = '\0';
  757. X    /* Try to find domain name in database. */
  758. X    for (domain = unk_domain.link; domain; domain = domain->link) {
  759. X        if (strEQ(domain->name, after_at)) {
  760. X        break;
  761. X        }
  762. X    }
  763. X    if (!domain) {        /* if domain doesn't exist, create it */
  764. X      register int len = cp - after_at + 1;
  765. X        domain = (DOMAIN *)safemalloc(sizeof (DOMAIN));
  766. X        total.domain++;
  767. X        domain->name = safemalloc(len);
  768. X        total.string2 += len;
  769. X        bcopy(after_at, domain->name, len);
  770. X        domain->ids = Nullart;
  771. X        domain->link = unk_domain.link;
  772. X        unk_domain.link = domain;
  773. X    }
  774. X    }
  775. X    /* Try to find id in this domain. */
  776. X    for (article = domain->ids; article; article = article->id_link) {
  777. X    if (strEQ(article->id, msg_id)) {
  778. X        break;
  779. X    }
  780. X    }
  781. X    if (!article) {        /* If it doesn't exist, create an article */
  782. X      register int len = strlen(msg_id) + 1;
  783. X    article = (ARTICLE *)safemalloc(sizeof (ARTICLE));
  784. X    bzero(article, sizeof (ARTICLE));
  785. X    total.article++;
  786. X    article->num = 0;
  787. X    article->id = safemalloc(len);
  788. X    total.string2 += len;
  789. X    bcopy(msg_id, article->id, len);
  790. X    article->domain = domain;
  791. X    article->id_link = domain->ids;
  792. X    domain->ids = article;
  793. X    }
  794. X    return article;
  795. X}
  796. X
  797. X/* Take all the data we've accumulated about the article and shove it into
  798. X** the article tree at the best place we can possibly imagine.
  799. X*/
  800. void
  801. insert_article(article, date)
  802. ARTICLE *article;
  803. time_t date;
  804. X{
  805. X    register ARTICLE *node, *last;
  806. X    register char *cp, *end;
  807. X    int len;
  808. X
  809. X    if (article->subject) {
  810. X    if (log_verbosity) {
  811. X        log_error("We've already seen article #%ld (%s@%s)\n",
  812. X        num, article->id, article->domain->name);
  813. X    }
  814. X    return;                        /* RETURN */
  815. X    }
  816. X    article->date = date;
  817. X    article->num = num;
  818. X    article->flags = 0;
  819. X
  820. X    if (!*references && found_Re) {
  821. X    if (log_verbosity > 1) {
  822. X        log_error("Missing reference line!  [%ld]\n", num);
  823. X    }
  824. X    }
  825. X    /* If the article has a non-zero root, it is already in a thread somewhere.
  826. X    ** Unlink it to try to put it in the best possible spot.
  827. X    */
  828. X    if (article->root) {
  829. X    /* Check for a real or shared-fake parent.  Articles that have never
  830. X    ** existed have a num of 0.  Expired articles that remain as references
  831. X    ** have a valid num.  (Valid date too, but no subject.)
  832. X    */
  833. X    for (node = article->parent;
  834. X         node && !node->num && node->child_cnt == 1;
  835. X         node = node->parent)
  836. X    {
  837. X        ;
  838. X    }
  839. X    unlink_child(article);
  840. X    if (node) {            /* do we have decent parents? */
  841. X        /* Yes: assume that our references are ok, and just reorder us
  842. X        ** with our siblings by date.
  843. X        */
  844. X        link_child(article);
  845. X        use_root(article, article->root);
  846. X        /* Freshen the date in any faked parent articles. */
  847. X        for (node = article->parent;
  848. X         node && !node->num && date < node->date;
  849. X         node = node->parent)
  850. X        {
  851. X        node->date = date;
  852. X        unlink_child(node);
  853. X        link_child(node);
  854. X        }
  855. X        return;                    /* RETURN */
  856. X    }
  857. X    /* We'll assume that this article has as good or better references
  858. X    ** than the child that faked us initially.  Free the fake reference-
  859. X    ** chain and process our references as usual.
  860. X    */
  861. X    for (node = article->parent; node; node = node->parent) {
  862. X        unlink_child(node);
  863. X        free_article(node);
  864. X    }
  865. X    article->parent = Nullart;        /* neaten up */
  866. X    article->siblings = Nullart;
  867. X    }
  868. X  check_references:
  869. X    if (!*references) {    /* If no references but "Re:" in subject, */
  870. X    if (found_Re) {    /* search for a reference in any cited text */
  871. X#ifndef SERVER
  872. X        for (len = 4; len && fgets(buff, sizeof buff, fp_article); len--) {
  873. X        if ((cp = index(buff, '<')) && (end = index(cp, ' '))) {
  874. X            if (end[-1] == ',') {
  875. X            end--;
  876. X            }
  877. X            *end = '\0';
  878. X            if ((end = index(cp, '>')) == Nullch) {
  879. X            end = cp + strlen(cp) - 1;
  880. X            }
  881. X            if (valid_message_id(cp, end)) {
  882. X            strcpy(references+1, cp);
  883. X            *references = ' ';
  884. X            if (log_verbosity > 2) {
  885. X                log_error("Found cited-text reference: '%s' [%ld]\n",
  886. X                references+1, num);
  887. X            }
  888. X            break;
  889. X            }
  890. X        }
  891. X        }
  892. X#endif
  893. X    } else {
  894. X        article->flags |= ROOT_ARTICLE;
  895. X    }
  896. X    }
  897. X    /* If we have references, process them from the right end one at a time
  898. X    ** until we either run into somebody, or we run out of references.
  899. X    */
  900. X    if (*references) {
  901. X    last = article;
  902. X    node = Nullart;
  903. X    end = references + strlen(references) - 1;
  904. X    while ((cp = rindex(references, '<')) != Nullch) {
  905. X        while (end >= cp && ((unsigned char)*end <= ' ' || *end == ',')) {
  906. X        end--;
  907. X        }
  908. X        end[1] = '\0';
  909. X        /* Quit parsing references if this one is garbage. */
  910. X        if (!valid_message_id(cp, end)) {
  911. X        if (log_verbosity) {
  912. X            log_error("Bad ref '%s' [%ld]\n", cp, num);
  913. X        }
  914. X        break;
  915. X        }
  916. X        /* Dump all domains that end in '.', such as "..." & "1@DEL." */
  917. X        if (end[-1] == '.') {
  918. X        break;
  919. X        }
  920. X        node = get_article(cp);
  921. X        *cp = '\0';
  922. X
  923. X        /* Check for duplicates on the reference line.  Brand-new data has
  924. X        ** no date.  Data we just allocated earlier on this line has a
  925. X        ** date but no root.  Special-case the article itself, since it
  926. X        ** MIGHT have a root.
  927. X        */
  928. X        if ((node->date && !node->root) || node == article) {
  929. X        if (log_verbosity) {
  930. X            log_error("Reference line contains duplicates [%ld]\n",
  931. X            num);
  932. X        }
  933. X        if ((node = last) == article) {
  934. X            node = Nullart;
  935. X        }
  936. X        continue;
  937. X        }
  938. X        last->parent = node;
  939. X        link_child(last);
  940. X        if (node->root) {
  941. X        break;
  942. X        }
  943. X        node->date = date;
  944. X        last = node;
  945. X        end = cp-1;
  946. X    }
  947. X    if (!node) {
  948. X        *references = '\0';
  949. X        goto check_references;
  950. X    }
  951. X    /* Check if we ran into anybody that was already linked.  If so, we
  952. X    ** just use their root.
  953. X    */
  954. X    if (node->root) {
  955. X        /* See if this article spans the gap between what we thought
  956. X        ** were two different roots.
  957. X        */
  958. X        if (article->root && article->root != node->root) {
  959. X        merge_roots(node->root, article->root);
  960. X        /* Set the roots of any children we brought with us. */
  961. X        set_root(article, node->root);
  962. X        }
  963. X        use_root(article, node->root);
  964. X    } else {
  965. X        /* We didn't find anybody we knew, so either create a new root or
  966. X        ** use the article's root if it was previously faked.
  967. X        */
  968. X        if (!article->root) {
  969. X        make_root(node);
  970. X        use_root(article, node->root);
  971. X        } else {
  972. X        node->root = article->root;
  973. X        link_child(node);
  974. X        use_root(article, article->root);
  975. X        }
  976. X    }
  977. X    /* Set the roots of the faked articles we created as references. */
  978. X    for (node = article->parent; node && !node->root; node = node->parent) {
  979. X        node->root = article->root;
  980. X    }
  981. X    /* Make sure we didn't circularly link to a child article(!), by
  982. X    ** ensuring that we run into the root before we run into ourself.
  983. X    */
  984. X    while (node && node->parent != article) {
  985. X        node = node->parent;
  986. X    }
  987. X    if (node) {
  988. X        /* Ugh.  Someone's tweaked reference line with an incorrect
  989. X        ** article-order arrived first, and one of our children is
  990. X        ** really one of our ancestors. Cut off the bogus child branch
  991. X        ** right where we are and link it to the root.
  992. X        */
  993. X        if (log_verbosity) {
  994. X        log_error("Found ancestral child -- fixing.\n");
  995. X        }
  996. X        unlink_child(node);
  997. X        node->parent = Nullart;
  998. X        link_child(node);
  999. X    }
  1000. X    } else {
  1001. X    /* The article has no references.  Either turn it into a new root, or
  1002. X    ** re-attach fleshed-out (previously faked) article to its old root.
  1003. X    */
  1004. X    if (!article->root) {
  1005. X        make_root(article);
  1006. X    } else {
  1007. X        link_child(article);
  1008. X        use_root(article, article->root);
  1009. X    }
  1010. X    }
  1011. X}
  1012. X
  1013. X/* Check if the string we've found looks like a valid message-id reference.
  1014. X*/
  1015. int
  1016. valid_message_id(start, end)
  1017. register char *start, *end;
  1018. X{
  1019. X    char *mid;
  1020. X
  1021. X    if (start == end) {
  1022. X    return 0;
  1023. X    }
  1024. X
  1025. X    if (*end != '>') {
  1026. X    /* Compensate for space cadets who include the header in their
  1027. X    ** subsitution of all '>'s into another citation character.
  1028. X    */
  1029. X    if (*end == '<' || *end == '-' || *end == '!' || *end == '%'
  1030. X     || *end == ')' || *end == '|' || *end == ':' || *end == '}'
  1031. X     || *end == '*' || *end == '+' || *end == '#' || *end == ']'
  1032. X     || *end == '@' || *end == '$') {
  1033. X        if (log_verbosity) {
  1034. X        log_error("Reference ended in '%c' [%ld]\n", *end, num);
  1035. X        }
  1036. X        *end = '>';
  1037. X    }
  1038. X    } else if (end[-1] == '>') {
  1039. X    if (log_verbosity) {
  1040. X        log_error("Reference ended in '>>' [%ld]\n", num);
  1041. X    }
  1042. X    *(end--) = '\0';
  1043. X    }
  1044. X    /* Id must be "<...@...>" */
  1045. X    if (*start != '<' || *end != '>' || (mid = index(start, '@')) == Nullch
  1046. X     || mid == start+1 || mid+1 == end) {
  1047. X    return 0;                    /* RETURN */
  1048. X    }
  1049. X    return 1;
  1050. X}
  1051. X
  1052. X/* Remove an article from its parent/siblings.  Leave parent pointer intact.
  1053. X*/
  1054. void
  1055. unlink_child(child)
  1056. register ARTICLE *child;
  1057. X{
  1058. X    register ARTICLE *last;
  1059. X
  1060. X    if (!(last = child->parent)) {
  1061. X    child->root->thread_cnt--;
  1062. X    if ((last = child->root->articles) == child) {
  1063. X        child->root->articles = child->siblings;
  1064. X    } else {
  1065. X        goto sibling_search;
  1066. X    }
  1067. X    } else {
  1068. X    last->child_cnt--;
  1069. X    if (last->children == child) {
  1070. X        last->children = child->siblings;
  1071. X    } else {
  1072. X        last = last->children;
  1073. X      sibling_search:
  1074. X        while (last->siblings != child) {
  1075. X        last = last->siblings;
  1076. X        }
  1077. X        last->siblings = child->siblings;
  1078. X    }
  1079. X    }
  1080. X}
  1081. X
  1082. X/* Link an article to its parent article.  If its parent pointer is zero,
  1083. X** link it to its root.  Sorts siblings by date.
  1084. X*/
  1085. void
  1086. link_child(child)
  1087. register ARTICLE *child;
  1088. X{
  1089. X    register ARTICLE *node;
  1090. X    register ROOT *root;
  1091. X
  1092. X    if (!(node = child->parent)) {
  1093. X    root = child->root;
  1094. X    root->thread_cnt++;
  1095. X    node = root->articles;
  1096. X    if (!node || child->date < node->date) {
  1097. X        child->siblings = node;
  1098. X        root->articles = child;
  1099. X    } else {
  1100. X        goto sibling_search;
  1101. X    }
  1102. X    } else {
  1103. X    node->child_cnt++;
  1104. X    node = node->children;
  1105. X    if (!node || child->date < node->date) {
  1106. X        child->siblings = node;
  1107. X        child->parent->children = child;
  1108. X    } else {
  1109. X      sibling_search:
  1110. X        for (; node->siblings; node = node->siblings) {
  1111. X        if (node->siblings->date > child->date) {
  1112. X            break;
  1113. X        }
  1114. X        }
  1115. X        child->siblings = node->siblings;
  1116. X        node->siblings = child;
  1117. X    }
  1118. X    }
  1119. X}
  1120. X
  1121. X/* Create a new root for the specified article.  If the current subject_str
  1122. X** matches any pre-existing root's subjects, we'll instead add it on as a
  1123. X** parallel thread.
  1124. X*/
  1125. void
  1126. make_root(article)
  1127. ARTICLE *article;
  1128. X{
  1129. X    register ROOT *new, *node;
  1130. X    register SUBJECT *subject;
  1131. X
  1132. X#ifndef NO_SUBJECT_MATCHING
  1133. X    /* First, check the other root's subjects for a match. */
  1134. X    for (node = root_root; node; node = node->link) {
  1135. X    for (subject = node->subjects; subject; subject = subject->link) {
  1136. X        if (subject_equal(subject->str, subject_str)) {
  1137. X        use_root(article, node);        /* use it instead */
  1138. X        link_child(article);
  1139. X        return;                    /* RETURN */
  1140. X        }
  1141. X    }
  1142. X    }
  1143. X#endif
  1144. X
  1145. X    /* Create a new root. */
  1146. X    new = (ROOT *)safemalloc(sizeof (ROOT));
  1147. X    total.root++;
  1148. X    new->articles = article;
  1149. X    new->root_num = article->num;
  1150. X    new->thread_cnt = 1;
  1151. X    if (article->num) {
  1152. X    article->author = new_author();
  1153. X    new->subject_cnt = 1;
  1154. X    new->subjects = article->subject = new_subject();
  1155. X    } else {
  1156. X    new->subject_cnt = 0;
  1157. X    new->subjects = Null(SUBJECT*);
  1158. X    }
  1159. X    article->root = new;
  1160. X    new->link = root_root;
  1161. X    root_root = new;
  1162. X}
  1163. X
  1164. X/* Add this article's subject onto the indicated root's list.  Point the
  1165. X** article at the root.
  1166. X*/
  1167. void
  1168. use_root(article, root)
  1169. ARTICLE *article;
  1170. ROOT *root;
  1171. X{
  1172. X    register SUBJECT *subject;
  1173. X    register ROOT *root2;
  1174. X    SUBJECT *hold, *child_subj = Null(SUBJECT*), *sib_subj = Null(SUBJECT*);
  1175. X    ARTICLE *node;
  1176. X
  1177. X    article->root = root;
  1178. X
  1179. X    /* If it's a fake, there's no subject to add. */
  1180. X    if (!article->num) {
  1181. X    return;                        /* RETURN */
  1182. X    }
  1183. X
  1184. X    /* If we haven't picked a unique message number to represent this root,
  1185. X    ** use the first non-zero number we encounter.  Which one doesn't matter.
  1186. X    */
  1187. X    if (!root->root_num) {
  1188. X    root->root_num = article->num;
  1189. X    }
  1190. X    article->author = new_author();
  1191. X
  1192. X    /* Check if the new subject matches any of the other subjects in this root.
  1193. X    ** If so, we just update the count.  If not, check all the other roots for
  1194. X    ** a match.  If found, the new subject is common between the two roots, so
  1195. X    ** we merge the two roots together.
  1196. X    */
  1197. X    root2 = root;
  1198. X#ifndef NO_SUBJECT_MATCHING
  1199. X    do {
  1200. X#endif
  1201. X    for (subject = root2->subjects; subject; subject = subject->link) {
  1202. X        if (subject_equal(subject->str, subject_str)) {
  1203. X        article->subject = subject;
  1204. X        subject->count++;
  1205. X#ifndef NO_SUBJECT_MATCHING
  1206. X        if (root2 != root) {
  1207. X            merge_roots(root, root2);
  1208. X        }
  1209. X#endif
  1210. X        return;                    /* RETURN */
  1211. X        }
  1212. X    }
  1213. X#ifndef NO_SUBJECT_MATCHING
  1214. X    if ((root2 = root2->link) == Null(ROOT*)) {
  1215. X        root2 = root_root;
  1216. X    }
  1217. X    } while (root2 != root);
  1218. X#endif
  1219. X
  1220. X    article->subject = hold = new_subject();
  1221. X    root->subject_cnt++;
  1222. X
  1223. X    /* Find the subject of any pre-existing children or siblings.  We want
  1224. X    ** to insert the new subject before one of these to keep the numbering
  1225. X    ** intuitive in the newsreader.
  1226. X    */
  1227. X    for (node = article->children; node; node = node->children) {
  1228. X    if (node->subject) {
  1229. X        child_subj = node->subject;
  1230. X        break;
  1231. X    }
  1232. X    }
  1233. X    for (node = article->siblings; node; node = node->siblings) {
  1234. X    if (node->subject) {
  1235. X        sib_subj = node->subject;
  1236. X        break;
  1237. X    }
  1238. X    }
  1239. X    if (!(subject = root->subjects)
  1240. X     || subject == child_subj || subject == sib_subj) {
  1241. X    hold->link = root->subjects;
  1242. X    root->subjects = hold;
  1243. X    } else {
  1244. X    while (subject->link
  1245. X     && subject->link != child_subj && subject->link != sib_subj) {
  1246. X        subject = subject->link;
  1247. X    }
  1248. X    hold->link = subject->link;
  1249. X    subject->link = hold;
  1250. X    }
  1251. X}
  1252. X
  1253. X/* Check subjects in a case-insignificant, punctuation-ignoring manner.
  1254. X*/
  1255. int
  1256. subject_equal(str1, str2)
  1257. register char *str1, *str2;
  1258. X{
  1259. X    register char ch1, ch2;
  1260. X
  1261. X    while ((ch1 = *str1++)) {
  1262. X    if (ch1 == ' ' || ispunct(ch1)) {
  1263. X        while (*str1 && (*str1 == ' ' || ispunct(*str1))) {
  1264. X        str1++;
  1265. X        }
  1266. X        ch1 = ' ';
  1267. X    } else if (isupper(ch1)) {
  1268. X        ch1 = tolower(ch1);
  1269. X    }
  1270. X    if (!(ch2 = *str2++)) {
  1271. X        return 0;
  1272. X    }
  1273. X    if (ch2 == ' ' || ispunct(ch2)) {
  1274. X        while (*str2 && (*str2 == ' ' || ispunct(*str2))) {
  1275. X        str2++;
  1276. X        }
  1277. X        ch2 = ' ';
  1278. X    } else if (isupper(ch2)) {
  1279. X        ch2 = tolower(ch2);
  1280. X    }
  1281. X    if (ch1 != ch2) {
  1282. X        return 0;
  1283. X    }
  1284. X    }
  1285. X    if (*str2) {
  1286. X    return 0;
  1287. X    }
  1288. X    return 1;
  1289. X}
  1290. X
  1291. X/* Create a new subject structure. */
  1292. SUBJECT *
  1293. new_subject()
  1294. X{
  1295. X    register int len = strlen(subject_str) + 1;
  1296. X    register SUBJECT *subject;
  1297. X
  1298. X    subject = (SUBJECT *)safemalloc(sizeof (SUBJECT));
  1299. X    total.subject++;
  1300. X    subject->count = 1;
  1301. X    subject->link = Null(SUBJECT*);
  1302. X    subject->str = safemalloc(len);
  1303. X    total.string1 += len;
  1304. X    bcopy(subject_str, subject->str, len);
  1305. X
  1306. X    return subject;
  1307. X}
  1308. X
  1309. X/* Create a new author structure. */
  1310. AUTHOR *
  1311. new_author()
  1312. X{
  1313. X    register len = strlen(author_str) + 1;
  1314. X    register AUTHOR *author, *last_author;
  1315. X
  1316. X    last_author = Null(AUTHOR*);
  1317. X    for (author = author_root; author; author = author->link) {
  1318. X#ifndef DONT_COMPARE_AUTHORS    /* might like to define this to save time */
  1319. X    if (strEQ(author->name, author_str)) {
  1320. X        author->count++;
  1321. X        return author;                /* RETURN */
  1322. X    }
  1323. X#endif
  1324. X    last_author = author;
  1325. X    }
  1326. X
  1327. X    author = (AUTHOR *)safemalloc(sizeof (AUTHOR));
  1328. X    total.author++;
  1329. X    author->count = 1;
  1330. X    author->link = Null(AUTHOR*);
  1331. X    author->name = safemalloc(len);
  1332. X    total.string1 += len;
  1333. X    bcopy(author_str, author->name, len);
  1334. X
  1335. X    if (last_author) {
  1336. X    last_author->link = author;
  1337. X    } else {
  1338. X    author_root = author;
  1339. X    }
  1340. X    return author;
  1341. X}
  1342. X
  1343. X/* Insert all of root2 into root1, setting the proper root values and
  1344. X** updating subject counts.
  1345. X*/
  1346. void
  1347. merge_roots(root1, root2)
  1348. ROOT *root1, *root2;
  1349. X{
  1350. X    register ARTICLE *node, *next;
  1351. X    register SUBJECT *subject;
  1352. X
  1353. X    /* Remember whoever's root num is lower.  This could screw up a
  1354. X    ** newsreader's kill-thread code if someone already saw the roots as
  1355. X    ** being separate, but it must be done.  The newsreader code will have
  1356. X    ** to handle this as best as it can.
  1357. X    */
  1358. X    if (root1->root_num > root2->root_num) {
  1359. X    root1->root_num = root2->root_num;
  1360. X    }
  1361. X
  1362. X    for (node = root2->articles; node; node = next) {
  1363. X    /* For each article attached to root2: detach it, set the branch's
  1364. X    ** root pointer to root1, and then attach it to root1.
  1365. X    */
  1366. X    next = node->siblings;
  1367. X    unlink_child(node);
  1368. X    node->siblings = Nullart;
  1369. X    set_root(node, root1);        /* sets children too */
  1370. X    /* Link_child() depends on node->parent being null and node->root
  1371. X    ** being set.
  1372. X    */
  1373. X    link_child(node);
  1374. X    }
  1375. X    root1->subject_cnt += root2->subject_cnt;
  1376. X    if (!(subject = root1->subjects)) {
  1377. X    root1->subjects = root2->subjects;
  1378. X    } else {
  1379. X    while (subject->link) {
  1380. X        subject = subject->link;
  1381. X    }
  1382. X    subject->link = root2->subjects;
  1383. X    }
  1384. X    unlink_root(root2);
  1385. X    free_root(root2);
  1386. X}
  1387. X
  1388. X/* When merging roots, we need to reset all the root pointers.
  1389. X*/
  1390. void
  1391. set_root(node, root)
  1392. ARTICLE *node;
  1393. ROOT *root;
  1394. X{
  1395. X    do {
  1396. X    node->root = root;
  1397. X    if (node->children) {
  1398. X        set_root(node->children, root);
  1399. X    }
  1400. X    } while (node = node->siblings);
  1401. X}
  1402. X
  1403. X/* Unlink a root from its neighbors. */
  1404. void
  1405. unlink_root(root)
  1406. register ROOT *root;
  1407. X{
  1408. X    register ROOT *node;
  1409. X
  1410. X    if ((node = root_root) == root) {
  1411. X    root_root = root->link;
  1412. X    } else {
  1413. X    while (node->link != root) {
  1414. X        node = node->link;
  1415. X    }
  1416. X    node->link = root->link;
  1417. X    }
  1418. X}
  1419. X
  1420. X/* Free an article and its message-id string.  All other resources must
  1421. X** already be free, and it must not be attached to any threads.
  1422. X*/
  1423. void
  1424. free_article(this)
  1425. ARTICLE *this;
  1426. X{
  1427. X    register ARTICLE *art;
  1428. X
  1429. X    if ((art = this->domain->ids) == this) {
  1430. X    if (!(this->domain->ids = this->id_link)) {
  1431. X        free_domain(this->domain);
  1432. X    }
  1433. X    } else {
  1434. X    while (this != art->id_link) {
  1435. X        art = art->id_link;
  1436. X    }
  1437. X    art->id_link = this->id_link;
  1438. X    }
  1439. X    total.string2 -= strlen(this->id) + 1;
  1440. X    free(this->id);
  1441. X    free(this);
  1442. X    total.article--;
  1443. X}
  1444. X
  1445. X/* Free the domain only when its last unique id has been freed. */
  1446. void
  1447. free_domain(this)
  1448. DOMAIN *this;
  1449. X{
  1450. X    register DOMAIN *domain;
  1451. X
  1452. X    if (this == (domain = &unk_domain)) {
  1453. X    return;
  1454. X    }
  1455. X    if (this == next_domain) {    /* help expire routine skip freed domains */
  1456. X    next_domain = next_domain->link;
  1457. X    }
  1458. X    while (this != domain->link) {
  1459. X    domain = domain->link;
  1460. X    }
  1461. X    domain->link = this->link;
  1462. X    total.string2 -= strlen(this->name) + 1;
  1463. X    free(this->name);
  1464. X    free(this);
  1465. X    total.domain--;
  1466. X}
  1467. X
  1468. X/* Free the subject structure and its string. */
  1469. void
  1470. free_subject(this)
  1471. SUBJECT *this;
  1472. X{
  1473. X    total.string1 -= strlen(this->str) + 1;
  1474. X    free(this->str);
  1475. X    free(this);
  1476. X    total.subject--;
  1477. X}
  1478. X
  1479. X/* Free a root.  It must already be unlinked. */
  1480. void
  1481. free_root(this)
  1482. ROOT *this;
  1483. X{
  1484. X    free(this);
  1485. X    total.root--;
  1486. X}
  1487. X
  1488. X/* Free the author structure when it's not needed any more. */
  1489. void
  1490. free_author(this)
  1491. AUTHOR *this;
  1492. X{
  1493. X    total.string1 -= strlen(this->name) + 1;
  1494. X    free(this->name);
  1495. X    free(this);
  1496. X    total.author--;
  1497. X}
  1498. X
  1499. X#if defined(SERVER) && !defined(USLEEP)
  1500. usleep(usec)
  1501. long usec;
  1502. X{
  1503. X# ifndef USELECT
  1504. X    if (usec /= 1000000) {
  1505. X    sleep((int)usec);
  1506. X    }
  1507. X# else
  1508. X    struct timeval t;
  1509. X
  1510. X    if (usec <= 0) {
  1511. X    return;
  1512. X    }
  1513. X    t.tv_usec = usec % 1000000;
  1514. X    t.tv_sec  = usec / 1000000;
  1515. X    (void) select(1, 0, 0, 0, &t);
  1516. X# endif
  1517. X}
  1518. X#endif
  1519. END_OF_FILE
  1520. if test 36510 -ne `wc -c <'mt-process.c'`; then
  1521.     echo shar: \"'mt-process.c'\" unpacked with wrong size!
  1522. fi
  1523. # end of 'mt-process.c'
  1524. fi
  1525. echo shar: End of archive 11 \(of 13\).
  1526. cp /dev/null ark11isdone
  1527. MISSING=""
  1528. for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 ; do
  1529.     if test ! -f ark${I}isdone ; then
  1530.     MISSING="${MISSING} ${I}"
  1531.     fi
  1532. done
  1533. if test "${MISSING}" = "" ; then
  1534.     echo You have unpacked all 13 archives.
  1535.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  1536. else
  1537.     echo You still need to unpack the following archives:
  1538.     echo "        " ${MISSING}
  1539. fi
  1540. ##  End of shell archive.
  1541. exit 0
  1542.